package com.coincalf.exchange.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.coincalf.exchange.entity.*;
import com.coincalf.exchange.mapper.ForexAccountMapper;
import com.coincalf.exchange.service.*;
import com.coincalf.framework.enums.BusinessType;
import com.coincalf.framework.enums.OpenPositionOrderStatus;
import com.coincalf.framework.enums.OrderType;
import com.coincalf.framework.enums.TradeType;
import com.coincalf.framework.exception.ForexAccountException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.List;

/**
 * <p>
 * 创新交易持仓信息 服务实现类
 * </p>
 *
 * @author qiang
 * @since 2018-05-12
 */
@Slf4j
@Service
public class ForexAccountServiceImpl extends ServiceImpl<ForexAccountMapper, ForexAccount> implements ForexAccountService {

    @Autowired
    private ForexOpenPositionOrderService forexOpenPositionOrderService;
    @Autowired
    private ForexClosePositionOrderService forexClosePositionOrderService;
    @Autowired
    private AccountService accountService;
    @Autowired
    private MarketService marketService;
    @Autowired
    private EntrustOrderService entrustOrderService;
    @Autowired
    private ForexAccountDetailService forexAccountDetailService;


    /**
     * 查询持仓账户
     *
     * @param userId    用户ID
     * @param marketId  交易对ID
     * @param orderType 持仓方向
     * @return
     */
    private ForexAccount queryByUserAndMarketAndType(long userId, long marketId, OrderType orderType) {
        EntityWrapper<ForexAccount> wrapper = new EntityWrapper<>();
        wrapper.eq("user_id", userId)
                .eq("market_id", marketId)
                .eq("type", orderType.getCode());
        return super.selectOne(wrapper);
    }

    /**
     * 创新交易平仓
     *
     * @param market       交易对
     * @param dealOrder    成交订单
     * @param entrustOrder 委托订单
     */
    @Override
    public void closePosition(Market market, TurnoverOrder dealOrder, EntrustOrder entrustOrder) {
        // 获取平仓订单类型
        OrderType orderType = OrderType.BUY;
        if (entrustOrder.getType() == OrderType.BUY.getCode()) {
            orderType = OrderType.SELL;
        }
        // 获取与委托单相反方向的持仓账户信息
        ForexAccount forexAccount = this.queryByUserAndMarketAndType(entrustOrder.getUserId(), entrustOrder.getMarketId(), orderType);
        if (forexAccount == null) {
            log.error("创新交易资金账户异常，user_id:{}, market_id:{}, type:{}",
                    entrustOrder.getUserId(),
                    entrustOrder.getMarketId(),
                    orderType.getCode());
            throw new ForexAccountException("创新交易资金账户异常");
        }
        // 待平仓量为0，则开仓
        if (forexAccount.getAmount().compareTo(BigDecimal.ZERO) == 0 && forexAccount.getLockAmount().compareTo(BigDecimal.ZERO) == 0) {
            // 计算持仓占用保证金
            BigDecimal lockMargin = entrustOrder.getLockMargin()
                    .multiply(dealOrder.getVolume())
                    .divide(entrustOrder.getVolume(), 8, BigDecimal.ROUND_HALF_UP);
            // 增加持仓信息
            forexAccount = this.queryByUserAndMarketAndType(entrustOrder.getUserId(), entrustOrder.getMarketId(), OrderType.getByCode(entrustOrder.getType()));
            this.addAmount(forexAccount.getId(), dealOrder, entrustOrder, lockMargin);
            return;
        }
        EntityWrapper<ForexOpenPositionOrder> openPositionOrderWrapper = new EntityWrapper<>();
        if (entrustOrder.getOpenOrderId() != null && entrustOrder.getOpenOrderId() > 0L) {
            openPositionOrderWrapper.eq("id", entrustOrder.getOpenOrderId());
        } else {
            openPositionOrderWrapper.eq("market_id", entrustOrder.getMarketId())
                    .eq("user_id", entrustOrder.getUserId())
                    .eq("type", orderType.getCode())
                    .eq("status", OpenPositionOrderStatus.OPEN.getCode());
            if (orderType == OrderType.BUY) {
                openPositionOrderWrapper.orderBy("price", true)
                        .orderBy("created", true);
            } else {
                openPositionOrderWrapper.orderBy("price", false)
                        .orderBy("created", true);
            }
        }
        // 成交数量
        BigDecimal volume = dealOrder.getVolume();
        // 获取待平仓列表
        List<ForexOpenPositionOrder> openPositionOrderList = forexOpenPositionOrderService.selectList(openPositionOrderWrapper);
        for (ForexOpenPositionOrder openPositionOrder : openPositionOrderList) {
            // 已平仓量
            BigDecimal closedVolume = openPositionOrder.getCloseNum();
            // 当前开仓订单的可平仓量
            BigDecimal canCloseVolume = openPositionOrder.getNum().subtract(closedVolume);
            // 可平仓量 > 当前成交量
            if (canCloseVolume.compareTo(volume) >= 0) {
                this.closePositionModifyAccount(market, openPositionOrder, dealOrder, entrustOrder, canCloseVolume, volume, forexAccount);
                break;
            }
            this.closePositionModifyAccount(market, openPositionOrder, dealOrder, entrustOrder, canCloseVolume, canCloseVolume, forexAccount);
            volume = volume.subtract(canCloseVolume);
        }
    }

    /**
     * 平仓更新账户资金
     *
     * @param market            交易对
     * @param openPositionOrder 开仓订单
     * @param dealOrder         成交订单
     * @param entrustOrder      委托订单
     * @param canCloseVolume    可平仓量
     * @param volume            当前平仓量
     * @param forexAccount      持仓账户
     * @throws ForexAccountException
     */
    private void closePositionModifyAccount(Market market,
                                            ForexOpenPositionOrder openPositionOrder,
                                            TurnoverOrder dealOrder,
                                            EntrustOrder entrustOrder,
                                            BigDecimal canCloseVolume,
                                            BigDecimal volume,
                                            ForexAccount forexAccount)
            throws ForexAccountException {
        Long userId = forexAccount.getUserId();
        long coinId = dealOrder.getBuyCoinId();
        // 成交价
        BigDecimal price = dealOrder.getPrice();
        // 计算平仓盈亏
        BigDecimal profit = this.getProfit(market, openPositionOrder, dealOrder, volume, entrustOrder);
        accountService.addProfit(userId, coinId, profit,
                BusinessType.FOREX_CLOSE_POSITION_PROFIT,
                BusinessType.FOREX_CLOSE_POSITION_PROFIT.getDesc(),
                dealOrder.getId());
        // 计算返还保证金
        // 每一手占用的保证金
        BigDecimal unlockMargin = openPositionOrder.getLockMargin()
                .multiply(volume)
                .divide(openPositionOrder.getNum().subtract(openPositionOrder.getCloseNum()), 8, BigDecimal.ROUND_HALF_UP);
        // 保存平仓信息
        ForexClosePositionOrder closePositionOrder = new ForexClosePositionOrder();
        closePositionOrder.setMarketId(entrustOrder.getMarketId())  // 交易对ID
                .setMarketName(market.getName())                    // 交易对名称
                .setUserId(entrustOrder.getUserId())                // 用户ID
                .setType(entrustOrder.getType())                    // 持仓方向
                .setAccountId(forexAccount.getId())                 // 外汇账户ID
                .setEntrustOrderId(entrustOrder.getId())            // 委托订单号
                .setOrderId(dealOrder.getId())                      // 成交订单号
                .setPrice(price)                                    // 成交价格
                .setNum(volume)                                     // 成交数量
                .setOpenId(openPositionOrder.getId())               // 关联开仓订单
                .setProfit(profit)                                  // 平仓盈亏
                .setUnlockMargin(unlockMargin);                     // 返还保证金
        forexClosePositionOrderService.insert(closePositionOrder);
        // 本次平仓量
        if (canCloseVolume.compareTo(volume) > 0) {
            // 部分平仓
            forexOpenPositionOrderService.closePosition(openPositionOrder.getId(), unlockMargin, volume, OpenPositionOrderStatus.OPEN);
        } else {
            // 已平仓
            forexOpenPositionOrderService.closePosition(openPositionOrder.getId(), unlockMargin, volume, OpenPositionOrderStatus.CLOSE);
        }
        // 获取开仓委托订单
        EntrustOrder openEntrustOrder = entrustOrderService.selectById(openPositionOrder.getEntrustOrderId());
        // 开仓订单
        this.modifyEntrustOrderLockMargin(openEntrustOrder, coinId, dealOrder.getOrderId(), unlockMargin);
        // 当前委托单
        entrustOrder = entrustOrderService.selectById(entrustOrder.getId());
        if (entrustOrder.getTradeType() == TradeType.OPEN_POSITION.getCode()) {
            // 如果是自己和自己成交，创建委托单时，没有冻结持仓量，需要临时冻结持仓量
            this.lockAmount(forexAccount.getId(), volume, TradeType.CLOSE_POSITION, "平仓冻结");
        }
        // 平仓解冻保证金
        this.modifyEntrustOrderLockMargin(entrustOrder, coinId, dealOrder.getOrderId(), unlockMargin);
        // 解冻持仓量
        this.unlockAmount(forexAccount.getId(), volume, TradeType.CLOSE_POSITION, "平仓解冻");
        // 平仓扣减持仓量
        this.subtractAmount(forexAccount.getId(), volume);
    }

    /**
     * 修改委托订单冻结的保证金
     *
     * @param entrustOrder 委托订单
     * @param coinId       币种ID
     * @param orderId      订单号
     * @param unlockMargin 解冻保证金
     */
    private void modifyEntrustOrderLockMargin(EntrustOrder entrustOrder, long coinId, long orderId, BigDecimal unlockMargin) {
        // 解冻开仓时的保证金
        if (entrustOrder.getTradeType() == TradeType.OPEN_POSITION.getCode()) {
            // 解冻部分保证金
            accountService.unlockAmount(entrustOrder.getUserId(), coinId, unlockMargin, BusinessType.FOREX_DEAL_MARGIN, orderId);
            entrustOrderService.modifyLockMargin(entrustOrder.getId(), unlockMargin);
        }
    }

    /**
     * 计算平仓盈亏
     *
     * @param market            交易对
     * @param openPositionOrder 开仓订单
     * @param TurnoverOrder     成交订单
     * @param closeNum          平仓量
     * @param EntrustOrder      委托订单
     * @return
     */
    private BigDecimal getProfit(
            Market market,
            ForexOpenPositionOrder openPositionOrder,
            TurnoverOrder TurnoverOrder,
            BigDecimal closeNum,
            EntrustOrder EntrustOrder
    ) {
        /**
         * 计算平仓盈亏
         * 多头持仓：（平仓成交价 - 开仓成交价） * 合约单位 * 报价货币兑（USDT或BTC）兑换率 * 手数
         * 空头持仓：（开仓成交价 - 平仓成交价）  * 合约单位 *  报价货币兑（USDT或BTC）兑换率 * 手数
         * 开买 100   平卖  120  赚
         * 开卖 100   平买  80   赚
         */
        BigDecimal priceCoinToBaseCoinRate = marketService.getPriceCoinToBaseCoinRate(market);
        BigDecimal profit;
        if (openPositionOrder.getType() == OrderType.BUY.getCode()) {
            // 多头持仓：（平仓成交价 - 开仓成交价） * 合约单位 * 报价货币兑（USDT或BTC）兑换率 * 手数
            profit = (TurnoverOrder.getPrice().subtract(openPositionOrder.getPrice()))      // 平仓成交价 - 开仓成交价
                    .multiply(closeNum)                                                     // 平仓量手数
                    .multiply(new BigDecimal(EntrustOrder.getContractUnit()))               // 合约单位
                    .multiply(priceCoinToBaseCoinRate)                                      // 报价货币兑（USDT或BTC）兑换率
                    .setScale(8, BigDecimal.ROUND_UP);
        } else {
            // 空头持仓：（开仓成交价 - 平仓成交价）  * 合约单位 *  报价货币兑（USDT或BTC）兑换率 * 手数
            profit = (openPositionOrder.getPrice().subtract(TurnoverOrder.getPrice()))      // 平仓成交价 - 开仓成交价
                    .multiply(closeNum)                                                     // 平仓量手数
                    .multiply(new BigDecimal(EntrustOrder.getContractUnit()))               // 合约单位
                    .multiply(priceCoinToBaseCoinRate)                                      // 报价货币兑（USDT或BTC）兑换率
                    .setScale(8, BigDecimal.ROUND_UP);
        }
        return profit;
    }

    /**
     * 冻结持仓
     *
     * @param accountId 持仓账户ID
     * @param volume    冻结金额
     * @param tradeType 交易类型
     * @param remark    备注
     * @return
     * @throws ForexAccountException
     */
    @Override
    public boolean lockAmount(long accountId,
                              BigDecimal volume,
                              TradeType tradeType,
                              String remark) throws ForexAccountException {
        if (baseMapper.lockAmount(accountId, volume) > 0) {
            ForexAccountDetail forexAccountDetail = this.generateForexAccountDetail(accountId, volume, tradeType, remark);
            forexAccountDetailService.insert(forexAccountDetail);
            return true;
        }
        throw new ForexAccountException("解冻持仓失败");
    }

    /**
     * 解冻持仓
     *
     * @param accountId 持仓账户ID
     * @param volume    冻结金额
     * @param tradeType 交易类型
     * @param remark    备注
     * @return
     * @throws ForexAccountException
     */
    @Override
    public boolean unlockAmount(long accountId,
                                BigDecimal volume,
                                TradeType tradeType,
                                String remark) throws ForexAccountException {
        if (baseMapper.unlockAmount(accountId, volume) > 0) {
            ForexAccountDetail forexAccountDetail = this.generateForexAccountDetail(accountId, volume, tradeType, remark);
            forexAccountDetailService.insert(forexAccountDetail);
            return true;
        }
        throw new ForexAccountException("解冻持仓失败");
    }

    /**
     * 增加持仓
     *
     * @param accountId    账户ID
     * @param dealOrder    成交订单
     * @param entrustOrder 委托订单
     * @param lockMargin   占用保证金
     * @return
     */
    private boolean addAmount(
            long accountId,
            TurnoverOrder dealOrder,
            EntrustOrder entrustOrder,
            BigDecimal lockMargin
    ) throws ForexAccountException {
        if (baseMapper.addAmount(accountId, dealOrder.getVolume()) > 0) {
            ForexAccountDetail forexAccountDetail = this.generateForexAccountDetail(accountId, dealOrder.getVolume(), TradeType.OPEN_POSITION, "开仓");
            forexAccountDetailService.insert(forexAccountDetail);
            // 保存开仓订单信息
            ForexOpenPositionOrder openPositionOrder = new ForexOpenPositionOrder();
            openPositionOrder.setUserId(entrustOrder.getUserId())       // 用户ID
                    .setMarketId(entrustOrder.getMarketId())            // 交易对ID
                    .setMarketName(entrustOrder.getMarketName())        // 交易对名称
                    .setCoinId(dealOrder.getBuyCoinId())                // 结算币种
                    .setType(entrustOrder.getType())                    // 持仓方向
                    .setAccountId(accountId)                            // 资金账户ID
                    .setEntrustOrderId(entrustOrder.getId())            // 委托订单号
                    .setOrderId(dealOrder.getId())                      // 成交订单号
                    .setPrice(dealOrder.getPrice())                     // 成交价格
                    .setNum(dealOrder.getVolume())                      // 成交数量
                    .setLockMargin(lockMargin)                          // 占用保证金
                    .setCloseNum(BigDecimal.ZERO)                       // 已平仓量
                    .setStatus(OpenPositionOrderStatus.OPEN.getCode()); // 状态：未平仓
            forexOpenPositionOrderService.insert(openPositionOrder);
            return true;
        }
        log.error("增加持仓失败，accountId：{}, amount：{}", accountId, dealOrder.getVolume());
        throw new ForexAccountException("增加持仓失败");
    }

    /**
     * 减少持仓
     *
     * @param accountId 账户ID
     * @param volume    持仓量
     * @return
     */
    private boolean subtractAmount(long accountId,
                                   BigDecimal volume) throws ForexAccountException {
        if (baseMapper.subtractAmount(accountId, volume) > 0) {
            ForexAccountDetail forexAccountDetail = this.generateForexAccountDetail(accountId, volume, TradeType.CLOSE_POSITION, "平仓");
            forexAccountDetailService.insert(forexAccountDetail);
            return true;
        }
        log.error("增加持仓失败，accountId：{}, amount:{}", accountId, volume);
        throw new ForexAccountException("增加持仓失败");
    }

    /**
     * 构建持仓账户流水
     *
     * @param accountId 持仓账户ID
     * @param amount    持仓量
     * @param remark    备注
     * @return
     */
    private ForexAccountDetail generateForexAccountDetail(long accountId, BigDecimal amount, TradeType tradeType, String remark) {
        ForexAccountDetail forexAccountDetail = new ForexAccountDetail();
        forexAccountDetail.setAccountId(accountId)              // 账户ID
                .setAmount(amount)                              // 持仓量
                .setType(tradeType.getCode())                   // 类型
                .setRemark(remark);                             // 备注
        return forexAccountDetail;
    }

}
